package com.bianmaba.spring.security.basic;

import com.bianmaba.spring.security.handler.AjaxSecurityHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.security.SecurityAuthorizeMode;
import org.springframework.boot.autoconfigure.security.SpringBootWebSecurityConfiguration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer;
import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer;
import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class BasicWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
    private static final Logger LOG = LoggerFactory.getLogger(BasicWebSecurityConfigurerAdapter.class);

    @Autowired
    public BasicSecurityProperties security;


    @Override
    public void configure(WebSecurity web) throws Exception {
        //静态免验证地址（适合用于静态资源或页面）
        List<String> paths = getIgnored();
        if (paths != null && !paths.isEmpty()) {
            List<RequestMatcher> matchers = new ArrayList<RequestMatcher>();
            if (!ObjectUtils.isEmpty(paths)) {
                for (String pattern : paths) {
                    matchers.add(new AntPathRequestMatcher(pattern, null));
                }
            }
            web.ignoring().requestMatchers(new OrRequestMatcher(matchers));
        }
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //配置是否使用SSL
        if (this.security.isRequireSsl()) {
            http.requiresChannel().anyRequest().requiresSecure();
        }
        //判断是否打开CSRF
        if (!this.security.isEnableCsrf()) {
            http.csrf().disable();
        }
        // 配置HEADERS相关
        SpringBootWebSecurityConfiguration.configureHeaders(http.headers(),
                this.security.getHeaders());


        //配置免验证地址
        configurePermitAll(http);

        //配置权限
        configureAuthority(http);

        //配置用户登录及异常处理相关
        configureLoginForm(http);

        //配置用户注销相关
        configureLogout(http);

        //配置SESSION管理
        configureSessionManagement(http.sessionManagement());

        if (security.isAnyRequestAuthenticated() == true) {
            http.authorizeRequests().anyRequest().authenticated();
        }

    }

    protected void configureLoginForm(HttpSecurity http) throws Exception {
        FormLoginConfigurer flc = http.formLogin();
        if (isAjax()) {
            flc.successHandler(new AjaxSecurityHandler());
            flc.failureHandler(new AjaxSecurityHandler());
            http.exceptionHandling().accessDeniedHandler(new AjaxSecurityHandler());
            flc.loginPage("/not_logon").loginProcessingUrl("/login");
        } else {
            if (!StringUtils.isEmpty(getLoginPage())) {
                flc.loginPage(getLoginPage());
            }
            if (!StringUtils.isEmpty(getLoginSuccessUrl())) {
                flc.defaultSuccessUrl(getLoginSuccessUrl());
            }
        }
        flc.permitAll();
    }

    protected void configureSessionManagement(SessionManagementConfigurer<HttpSecurity> sessionManagement) {
        sessionManagement.sessionAuthenticationFailureHandler(new AjaxSecurityHandler());
        sessionManagement.maximumSessions(1).expiredSessionStrategy(new AjaxSecurityHandler());
    }

    protected void configureAuthority(HttpSecurity http) throws Exception {
        //配置需要权限访问的路径
        List<Map<String, String>> resources = getResources();
        if (!resources.isEmpty()) {
            SecurityAuthorizeMode mode = this.security.getBasic().getAuthorizeMode();
            for (Map<String, String> resource : resources) {
                String[] roles = resource.get("roles").split("[,]");
                String path = resource.get("path");
                if (mode == null || mode == SecurityAuthorizeMode.ROLE) {
                    http.authorizeRequests().antMatchers(path).hasAnyRole(roles);
                } else if (mode == SecurityAuthorizeMode.AUTHENTICATED) {
                    http.authorizeRequests().antMatchers(path).authenticated();
                }
            }
        }
    }

    protected void configurePermitAll(HttpSecurity http) throws Exception {
        // permitAll，会给没有登录的用户适配一个AnonymousAuthenticationToken，设置到SecurityContextHolder，方便后面的filter可以统一处理authentication。
        List<String> permited = getPermited();
        if (permited != null && !permited.isEmpty()) {
            List<String> matchers = new ArrayList<String>();
            if (!ObjectUtils.isEmpty(permited)) {
                for (String pattern : permited) {
                    matchers.add(pattern);
                }
            }
            http.authorizeRequests().antMatchers(matchers.toArray(new String[matchers.size()])).permitAll();
        }
    }


    protected void configureLogout(HttpSecurity http) throws Exception {
        LogoutConfigurer<HttpSecurity> lc = http.logout();
        if (isAjax()) {
            lc.logoutSuccessHandler(new AjaxSecurityHandler());
        } else {
            if (!StringUtils.isEmpty(getLogoutSuccessUrl())) {
                lc.logoutSuccessUrl(getLogoutSuccessUrl());
            }
        }
        lc.permitAll();
    }


    @Autowired
    protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        List<Map<String, String>> users = new ArrayList<>(getUsers());
        if (users == null) {
            return;
        }

        for (Map<String, String> user : users) {
            String username = user.get("username");
            String password = user.get("password");
            String[] roles = user.get("roles").split("[,]");
            auth.inMemoryAuthentication().withUser(username).password(password).roles(roles);
        }
    }

    /**
     * 静态免验证地址
     * <pre>
     * 默认为："/css/**", "/js/**","/images/**", "/webjars/**",  "**.css", "**.js", "**.jpg", "**.png", "**.gif", "**.bmp", "**.icon"
     * 可使用yml配置
     *  security:
     *      ignored:
     *          - "**.jpg"
     *          - "**.gif"
     *          - "**.png"
     *          ......
     * </pre>
     *
     * @return
     */
    public List<String> getIgnored() {
        return new ArrayList<>(security.getIgnored());
    }

    /**
     * 非静态免验证地址：permitAll 会给没有登录的用户适配一个AnonymousAuthenticationToken，设置到SecurityContextHolder，方便后面的filter可以统一处理authentication。
     * 默认为空
     * 可使用yml配置
     * <pre>
     *  security:
     *      permited:
     *          - "**.read"
     *          ......
     * </pre>
     *
     * @return
     */
    public List<String> getPermited() {
        return new ArrayList<String>(security.getPermited());
    }

    /**
     * 本地用户信息
     * <pre>
     * 用户包含：username,password,roles
     * 默认用户配置:
     * 1.{username:admin，password:admin,roles:admin，user}
     * 2.{username:user,password:user,roles:user}
     * 也可使用yml配置：
     * security:
     *   basic:
     *      resources:
     *         - username: admin
     *           password: admin
     *           roles: admin,user
     * </pre>
     *
     * @return
     */
    public List<Map<String, String>> getUsers() {
        List<Map<String, String>> users = new ArrayList<Map<String, String>>(security.getBasic().getUsers());
        if (users.isEmpty()) {
            Map<String, String> user = new HashMap<String, String>(0);
            user.put("username", "admin");
            user.put("password", "admin");
            user.put("roles", "admin,user");
            LOG.info("用户配置信息(security.basic.users)不存在，添加默认用户(用户名：admin,密码：admin,角色：admin&user)。");
            users.add(user);
            user = new HashMap<String, String>(0);
            user.put("username", "user");
            user.put("password", "user");
            user.put("roles", "user");
            LOG.info("用户配置信息(security.basic.users)不存在，添加默认用户(用户名：user,密码：user,角色：user)。");
            users.add(user);
        }
        return users;
    }


    /**
     * 本地资源信息
     * <pre>
     * 资源包含：path,roles
     * 默认资源配置: {path:/**，roles：admin,user}
     * 也可使用yml配置：
     * 多用户：security.basic.resource下配置List<MAP>
     * 单用户也可如下:
     * security:
     *   basic:
     *      resources:
     *         - path: /**
     *           roles:admin,user
     * </pre>
     *
     * @return
     */
    public List<Map<String, String>> getResources() {
        List<Map<String, String>> resources = new ArrayList<Map<String, String>>(security.getBasic().getResources());
        if (resources.isEmpty()) {
            Map<String, String> resource = new HashMap<String, String>(0);
            resource.put("path", "/**");
            resource.put("roles", "admin,user");
            resources.add(resource);
            LOG.info("资源配置信息(security.basic.resources)不存在，添加默认资源(路径：/**,角色：admin&user)。");
        }
        return resources;
    }

    public String getLoginPage() {
        return security.getLoginPage();
    }

    public String getLoginSuccessUrl() {
        return security.getLoginSuccessUrl();
    }

    public String getLogoutSuccessUrl() {
        return security.getLogoutSuccessUrl();
    }

    public boolean isAjax() {
        return security.isAjax();
    }
}
